/* Copyright 2008. All rights reserved. */
package org.bling.parser.expressions;

import java.util.Iterator;

import org.apache.commons.jexl.Expression;
import org.apache.commons.jexl.ExpressionFactory;
import org.apache.commons.jexl.JexlContext;
import org.apache.commons.jexl.JexlHelper;
import org.bling.process.context.Context;

public class ExpressionManager {

	public static final String EXPRESSION_START = "${";
	public static final String EXPRESSION_END   = "}";
	
	private ExpressionManager() {
		
	}
	
	public static Object evaluate(String expression, Context context) {
		
		System.out.println(expression);
		
		int start = expression.indexOf(EXPRESSION_START);
		
		if (start == -1) {
			return expression;
		}

		// Remove the ${ from the string, and look for more expressions
		String sub = expression.substring(start+EXPRESSION_START.length());
		
		if (!sub.contains(EXPRESSION_START)) {
			// Simple expression, with only one ${variable}
			int end = expression.indexOf(EXPRESSION_END);
			// Pull off ${ and }, and pass the expression to JEXL
			sub = expression.substring(start + EXPRESSION_START.length(), end
					- EXPRESSION_END.length() + 1);
			Object value = lookupInContext(sub, context);

			// If the expression was all that was in the string, we're done
			if (start == 0 && end == expression.length() - 1) {
				System.out
						.println("expression takes up the whole string... returning result");
				return value;
			}

			// otherwise, we need to add the prefix/suffix
			String prefix = expression.substring(0, start);
			String suffix = expression.substring(end + 1);

			System.out
					.println("expression does NOT take up the whole string...");
			System.out.println("prefix=" + prefix);
			System.out.println("suffix=" + suffix);

			return prefix + value.toString() + suffix;

		} else {
			System.out.println("Multiple...");
			// Complex expression, with nested ${var${iables}} or multiple
			// ${var}${iables}
			if (nextEndBeforeNextStart(sub)) {
				
				int end = expression.indexOf(EXPRESSION_END);
				// Pull off ${ and }, and pass the expression to JEXL
				sub = expression.substring(start + EXPRESSION_START.length(), end
						- EXPRESSION_END.length() + 1);
				Object value = lookupInContext(sub, context);

				String prefix = expression.substring(0, start);
				String suffix = expression.substring(end + 1);

				System.out.println("prefix=" + prefix);

				return prefix + value.toString() + evaluate(suffix, context).toString();
			}
			else {
				throw new RuntimeException("Nested Expressions Not Supported");
			}
		}
		
	}

	private static Object lookupInContext(String key, Context context) {
		try {
			Expression e = ExpressionFactory.createExpression(key);
			JexlContext jc = toContext(context);
			return e.evaluate(jc);
		} catch (Exception e) {

		}
		return null;
	}
	
	private static JexlContext toContext(Context context) {
		JexlContext jc = JexlHelper.createContext();
		
		Iterator<String> keys = context.keySet().iterator();
		while (keys.hasNext()) {
			String key = keys.next();
			jc.getVars().put(key, context.getAttribute(key));
		}
		
		return jc;
	}

	private static boolean nextEndBeforeNextStart(String expression) {
		// If a close expression } is found before the next start expression ${
		int start = expression.indexOf(EXPRESSION_START);
		int end = expression.indexOf(EXPRESSION_END);
		
		if (start == -1) return true;
		if (start < end) return false;
		
		return true;
	}
}
